﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using System.Xml.XPath;
using Microsoft.Win32;

namespace Microsoft.Tools.CodeRunner
{

    internal enum ScaffoldLanguage
    {
        Unknown,
        CSharp,
        VisualBasic,
        JScript,
        JSharp
    };

    /// <summary>
    /// Scaffold task
    /// </summary>
    [CommandLineCopyright("CopyrightMicrosoft")]
    [CommandLineTitle("ScaffoldTitle")]
    [CommandLineDescription("ScaffoldDescription")]
    public class Scaffold : IProcessCommandLine, IProcessConfiguration, IProcessEnvironment
    {
        #region Private Fields
        private bool showHelp = false;
        private ParsedPath scriptPath = null;
        private ParsedPath programTemplate = null;
        private bool verbose = true;
        private bool scrapeReferences = true;
        private ScaffoldLanguage language = ScaffoldLanguage.Unknown;
        private CommandLineParser parser = null;
        private bool runningFromCommandLine = false;
        private bool wait = false;
        private bool noRemoting = false;
        private string remotingUrl = null;
        private static readonly string scaffoldEnvironmentVar = "SCAFFOLD_CONFIG";
        private static readonly string scaffoldExe = "scaffold.exe";
        private OutputHelper output;

        #endregion

        #region Private Properties
        internal CommandLineParser Parser
        {
            get
            {
                if (parser == null)
                    parser = new CommandLineParser(typeof(Scaffold), typeof(ScaffoldResources), CommandLineParserFlags.None);

                return parser;
            }
        }

        public OutputHelper Output 
        { 
            get { return output; }
        }

        #endregion

        #region Construction
        /// <summary>
        /// Defoult constructor
        /// </summary>
        public Scaffold()
        {
        }
        
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="outputter">IOutputter to use</param>
        public Scaffold(IOutputter outputter)
        {
            this.output = new OutputHelper(outputter, CsrResources.ResourceManager);
        }

        #endregion    

        #region Public Properties

        /// <summary>
        /// Show help for the task
        /// </summary>
        [CommandLineArgument("help", Description = "HelpSwitchDescription", ShortName = "?", ValueHint = "")]
        public bool ShowHelp
        {
            get
            {
                return showHelp;
            }
            set
            {
                showHelp = value;
            }
        }

        /// <summary>
        /// Verbose output
        /// </summary>
        [CommandLineArgument("verbose", Description = "VerboseSwitchDescription", ShortName = "v", ValueHint = "")]
        public bool Verbose
        {
            get
            {
                return verbose;
            }
            set
            {
                verbose = value;
            }
        }

        /// <summary>
        /// Wait for VS instance to finish before returning
        /// </summary>
        [CommandLineArgument("wait", Description = "WaitSwitchDescription", ShortName = "w", ValueHint = "")]
        public bool Wait
        {
            get { return wait; }
            set { wait = value; }
        }

        [CommandLineArgument("template", Description = "ScriptTemplateSwitchDescription", ShortName = "t", ValueHint = "ScriptTemplateSwitchHint")]
        internal ParsedPath ScriptTemplate
        {
            get
            {
                return programTemplate;
            }
            set
            {
                programTemplate = value;
            }
        }
        
        [DefaultCommandLineArgument("script", ValueHint = "ScriptSwitchHint")]
        internal ParsedPath ScriptPath
        {
            get
            {
                return scriptPath;
            }
            set
            {
                scriptPath = value;
                language = GetScaffoldLanguageFromExtension(scriptPath.Extension);
            }
        }

        /// <summary>
        /// Scrape references after closing VS instance
        /// </summary>
        public bool ScrapeReferences 
        { 
            get { return scrapeReferences; }
            set
            {
                scrapeReferences = value;
            }
        }

        /// <summary>
        /// Do not use remoting to start a hidden instance of scaffold
        /// </summary>
        public bool NoRemoting 
        { 
            set
            {
                noRemoting = value;
            }
            get
            {
                return noRemoting;
            } 
        }

        internal string Language
        {
            set
            {
                language = (ScaffoldLanguage)Enum.Parse(typeof(ScaffoldLanguage), value, true);
            }
        }

        internal string RemotingUrl 
        { 
            get { return remotingUrl; } 
            set { remotingUrl = value; } 
        } 

        internal ScaffoldLanguage _Language
        {
            get { return language; }
        }
        
        #endregion

        #region Private Classes
        private class VisualStudioInfo
        {
            public VisualStudioInfo(string devEnvExe)
            {
                this.devEnvExe = devEnvExe;
            }

            private FileVersionInfo version;
            private string devEnvExe;

            public FileVersionInfo Version
            {
                get
                {
                    if (version == null)
                    {
                        version = FileVersionInfo.GetVersionInfo(devEnvExe);
                    }

                    return version;
                }
            }

            public string DevEnvExe
            {
                get
                {
                    return devEnvExe;
                }
            }
        }

        #endregion

        #region Private Methods

		private VisualStudioInfo GetVisualStudioInfo()
		{
			string exeName = null;
			string[] vsRegistryRoots = 
			{
				@"SOFTWARE\Microsoft\VisualStudio\9.0",
				@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0",
				@"SOFTWARE\Microsoft\VisualStudio\8.0",
				@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\8.0",
			};
			
			RegistryKey rk = null;
			
			for (int i = 0; i < vsRegistryRoots.Length; ++i)
			{
				rk = Registry.LocalMachine.OpenSubKey(vsRegistryRoots[i]);
				
				if (rk != null)
				{
					// We have a key, but do we have the Installdir value?  If we are running on x64, then 
					// the actual 32-bit VS registry keys will be under Wow6432Node.
					try
					{
						object obj = rk.GetValue("InstallDir");

						if (obj != null)
						{
							exeName = obj.ToString();
							exeName += (exeName.EndsWith(@"\") ? "" : @"\") + "devenv.exe";
							
							break;
						}
					}
					catch (Exception e)
					{
						if (!(e is SecurityException ||
							e is UnauthorizedAccessException ||
							e is IOException))
						{
							throw e;
						}
					}
				}
			}

			if (exeName == null)
			{
				Output.Error(ScaffoldResources.VSNotInstalled);
				return null;
			}
			
			return new VisualStudioInfo(exeName);
		}

        private ScaffoldLanguage GetScaffoldLanguageFromExtension(string ext)
        {
            switch (ext)
            {
                case ".cs":
                case ".csr":
                    return ScaffoldLanguage.CSharp;

                default:
                    return ScaffoldLanguage.Unknown;
            }
        }

        private string GetResource(string s)
        {
            Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(s);
            StreamReader sr = new StreamReader(stream, Encoding.ASCII);

            return sr.ReadToEnd();
        }

        private void CreateFile(string fileName, string content)
        {
            if (File.Exists(fileName))
            {
                Output.Warning(ScaffoldResources.AlreadyExists, fileName);
                return;
            }

            using (StreamWriter sw = new StreamWriter(fileName, false, System.Text.Encoding.ASCII))
            {
                sw.Write(content);
            }

            if (this.Verbose)
                Output.Message(MessageImportance.Low, ScaffoldResources.Created, fileName);
        }

        private void DeleteDirectory(string dirName)
        {
            try
            {
                Directory.Delete(dirName, true);

                if (this.Verbose)
                    Output.Message(MessageImportance.Low, ScaffoldResources.SubDirDeleted, dirName);
            }
            catch (SystemException)
            {
                Output.Warning(ScaffoldResources.SubDirNotDeleted, dirName);
            }
        }

        private ArrayList GrabCsProjFileReferences(string csprojFile)
        {
            ArrayList paths = new ArrayList();
            ParsedPath parsedCsprojFile = new ParsedPath(csprojFile);

            try
            {
                XPathDocument doc = new XPathDocument(csprojFile);
                XPathNavigator nav = doc.CreateNavigator();
                XmlNamespaceManager nm = new XmlNamespaceManager(nav.NameTable);

                nm.AddNamespace("ns", "http://schemas.microsoft.com/developer/msbuild/2003");

                // For all the assemblies that DO have HintPath's we should use that, because those are most
                // likely the ones that we put there, so we need to remove them.
                XPathNodeIterator iter = nav.Select("//ns:Reference/ns:HintPath/text()", nm);

                while (iter.MoveNext())
                {
                    paths.Add(new ParsedPath(iter.Current.Value).MakeFullPath(parsedCsprojFile));
                }

                // TODO-johnls-12/15/2007: To get the list of other assemblies, we can use a query like: 
                //
                //   XPathNodeIterator iter = nav.Select("//ns:Reference[not(ns:HintPath)]/@Include", nm);
                //
                // This is the Include attribute of all Reference elements with no HintPath child element.
                // This will give us a list of assembly names, but no path information.  
                // It seems like if the assembly is in the GAC it doesn't get any HintPath information.
                // We need to figure out how VS goes about finding these assemblies.
            }
            catch (XPathException)
            {
                // This would only happen if the user manually edited the .csproj file - which is a possibility
                Output.Error(ScaffoldResources.UnableToScrapeRefs, csprojFile);
            }

            return paths;
        }

        private ArrayList GrabRspFileReferences(ParsedPath rspFile)
        {
            string text;
            StringBuilder sb = new StringBuilder();

            using (StreamReader reader = new StreamReader(rspFile))
            {
                text = reader.ReadToEnd();
            }

            Regex r = new Regex("/r[^:]*:(.*)$", RegexOptions.IgnoreCase | RegexOptions.Multiline);

            Match m = r.Match(text);

            ArrayList paths = new ArrayList();

            while (m.Success)
            {
                string s = m.Groups[1].Value;

                // Chop off trailing '\r'; won't be there if last line of file doesn't end in \r\n
                if (s[s.Length - 1] == '\r')
                    s = s.Substring(0, s.Length - 1);

                s = Environment.ExpandEnvironmentVariables(s);

                paths.Add(new ParsedPath(s).MakeFullPath(rspFile));

                m = m.NextMatch();
            }

            return paths;
        }

        private string CreateXmlSnippetFromReferences(ArrayList paths)
        {
            StringBuilder sb = new StringBuilder();

            foreach (ParsedPath path in paths)
            {
                sb.AppendFormat(
                    "  <Reference Include=\"{0}\">" + System.Environment.NewLine +
                    "    <HintPath>{1}</HintPath>" + System.Environment.NewLine +
                    "  </Reference>" + System.Environment.NewLine,
                    path.File,
                    path.All);
            }

            return sb.ToString();
        }

        private bool CreateProjectFiles(VisualStudioInfo vsInfo, ParsedPath projectDir)
        {
            if (!Directory.Exists(this.ScriptPath.RootAndDirectory))
            {
                Output.Error(ScaffoldResources.DirectoryDoesNotExist, this.ScriptPath.RootAndDirectory);
                return false;
            }

            // Set publicly visible script path (in this AppDomain).  This also sets the script.rsp file name.
            ScriptEnvironment.ScriptPath = this.ScriptPath;
            ListDictionary dict = new ListDictionary();
            ParsedPath localRspFile = ScriptEnvironment.ScriptRspPath;
            ParsedPath sharedRspFile = ScriptEnvironment.SharedRspPath;
            ParsedPath runtimeRspFile = ScriptEnvironment.CompilerRspPath;

            dict.Add("BaseName", this.ScriptPath.File);
            dict.Add("SourceDirectory", this.ScriptPath.RootAndDirectory);
            dict.Add("SourceName", this.ScriptPath.FileAndExtension);
            dict.Add("NewProjectGuid", Guid.NewGuid().ToString().ToUpper());

            string versionNumber = String.Format("{0}.{1}.{2}",
                vsInfo.Version.ProductMajorPart, vsInfo.Version.ProductMinorPart, vsInfo.Version.ProductBuildPart);

            dict.Add("VSVersion", versionNumber);

            if (vsInfo.Version.ProductMajorPart == 9)
            {
                dict.Add("ToolsVersion", " ToolsVersion=\"3.5\" ");
                dict.Add("SolutionFileVersion", "10.00");
                dict.Add("VSName", "Codename Orcas");
            }
            else
            {
                dict.Add("ToolsVersion", "");
                dict.Add("SolutionFileVersion", "9.00");
                dict.Add("VSName", "2005");
            }

            string refs;

            if (File.Exists(localRspFile))
            {
                if (this.Verbose)
                    Output.Message(MessageImportance.Low, ScaffoldResources.ResponseFileProcessed, localRspFile);

                refs = CreateXmlSnippetFromReferences(GrabRspFileReferences(localRspFile));
            }
            else
                refs = "";

            dict.Add("LocalReferences", refs);

            if (File.Exists(sharedRspFile))
            {
                if (this.Verbose)
                    Output.Message(MessageImportance.Low, ScaffoldResources.ResponseFileProcessed, sharedRspFile);

                refs = CreateXmlSnippetFromReferences(GrabRspFileReferences(sharedRspFile));
            }
            else
                refs = "";

            dict.Add("SharedReferences", refs);

            if (File.Exists(runtimeRspFile))
            {
                if (this.Verbose)
                    Output.Message(MessageImportance.Low, ScaffoldResources.ResponseFileProcessed, runtimeRspFile);

                refs = CreateXmlSnippetFromReferences(GrabRspFileReferences(runtimeRspFile));
            }
            else
                refs = "";

            dict.Add("RuntimeReferences", refs);

            string tagProgramFile = this.ScriptTemplate;

            if (tagProgramFile == null || !File.Exists(tagProgramFile))
                tagProgramFile = GetResource("Microsoft.Tools.CodeRunner.Templates.Template.cs");

            string sourceFile = StringUtility.ReplaceTags(tagProgramFile, "%", "%", dict);

            string tagCsProjFile = GetResource("Microsoft.Tools.CodeRunner.Templates.Template.csproj");
            string csProjFile = StringUtility.ReplaceTags(tagCsProjFile, "%", "%", dict);

            string tagCsProjUserFile = GetResource("Microsoft.Tools.CodeRunner.Templates.Template.csproj.user");
            string csProjUserFile = StringUtility.ReplaceTags(tagCsProjUserFile, "%", "%", dict);

            string tagSlnFile = GetResource("Microsoft.Tools.CodeRunner.Templates.Template.sln");
            string slnFile = StringUtility.ReplaceTags(tagSlnFile, "%", "%", dict);

            try
            {
                if (!File.Exists(this.ScriptPath))
                    CreateFile(this.ScriptPath, sourceFile);

                CreateFile(projectDir.RootAndDirectory + this.ScriptPath.File + ".csproj", csProjFile);
                CreateFile(projectDir.RootAndDirectory + this.ScriptPath.File + ".csproj.user", csProjUserFile);
                CreateFile(projectDir.RootAndDirectory + this.ScriptPath.File + ".sln", slnFile);
            }
            catch (IOException exp)
            {
                Output.Error("{0}", exp.Message);
                return false;
            }

            return true;
        }

        private void RemoveDuplicateRefs(ArrayList originalRefs, ref ArrayList newRefs)
        {
            // Remove each entry in the new reference list that is in the original one
            foreach (ParsedPath path in originalRefs)
            {
                int j = newRefs.BinarySearch(path, null);

                if (j >= 0)
                    newRefs.RemoveAt(j);
            }
        }

        private void DeleteProjectFiles(ParsedPath projectDir)
        {
            ParsedPath localRspFile = ScriptEnvironment.ScriptRspPath;
            ParsedPath sharedRspFile = ScriptEnvironment.SharedRspPath;
            ParsedPath runtimeRspFile = ScriptEnvironment.CompilerRspPath;
            string projName = projectDir.RootAndDirectory + this.ScriptPath.File + ".csproj";

            if (File.Exists(projName) && this.ScrapeReferences)
            {
                ArrayList newRefs = GrabCsProjFileReferences(projName);

                // This will do an alphabetical sort of the list
                newRefs.Sort();

                // Remove all references that are already in the run-times csc.rsp
                if (File.Exists(runtimeRspFile))
                {
                    RemoveDuplicateRefs(GrabRspFileReferences(runtimeRspFile), ref newRefs);
                }

                // Remove all references that are already in the shared csc.rsp
                if (File.Exists(sharedRspFile))
                {
                    RemoveDuplicateRefs(GrabRspFileReferences(sharedRspFile), ref newRefs);
                }

                // Remove all references that are already in the program's .rsp
                if (File.Exists(localRspFile))
                {
                    RemoveDuplicateRefs(GrabRspFileReferences(localRspFile), ref newRefs);
                }

                // If anything is left it gets added into the programs response file
                if (newRefs.Count > 0)
                {
                    try
                    {
                        string s = String.Empty;

                        if (File.Exists(localRspFile))
                        {
                            using (StreamReader reader = new StreamReader(localRspFile))
                            {
                                // Check that the file has a \r\n at the end!
                                s = reader.ReadToEnd();

                                if (!s.EndsWith("\r\n"))
                                    s += "\r\n";
                            }
                        }

                        using (StreamWriter writer = new StreamWriter(localRspFile))
                        {
                            writer.Write(s);

                            foreach (ParsedPath path in newRefs)
                            {
                                writer.WriteLine("/r:\"{0}\"", path);

                                if (this.Verbose)
                                    Output.Message(MessageImportance.Low, ScaffoldResources.AddingToScriptRsp, path.ToString());
                            }
                        }
                    }
                    catch (IOException)
                    {
                        Output.Warning(ScaffoldResources.FileCouldNotBeUpdated, localRspFile);
                    }
                }
            }

            DeleteDirectory(projectDir);
        }

        private bool StartDevenvAndWait(VisualStudioInfo vsInfo, ParsedPath solutionFile)
        {
            // Now try and start devenv
            Process process = null;

            // Clear undocumented MS build environment variables that will confuse VS if set
            Environment.SetEnvironmentVariable("COMPLUS_INSTALLROOT", "");
            Environment.SetEnvironmentVariable("COMPLUS_VERSION", "");

            try
            {
                if (this.Verbose)
                    Output.Message(MessageImportance.Low, ScaffoldResources.StartingVS);

                ProcessStartInfo startInfo = new ProcessStartInfo(
                    vsInfo.DevEnvExe, "\"" + solutionFile + "\" \"" + this.ScriptPath + "\"");

                startInfo.WorkingDirectory = solutionFile.RootAndDirectory;
                startInfo.UseShellExecute = false;

                process = Process.Start(startInfo);
            }
            catch (Win32Exception e)
            {
                Output.Error(ScaffoldResources.UnableToStartVS, solutionFile, e.Message);
                return false;
            }

            // Devenv has started.  Wait for it to exit.
            if (process != null)
            {
                if (this.Verbose && this.Wait)
                    Output.Message(MessageImportance.Low, ScaffoldResources.WaitingForVS);

                // At this point we free the server/parent scaffold process so that it can 
                // exit and return control of the console to the user.  Any logging after this 
                // point might silently fail if the remote build engine decides to away, but that's OK.
                Output.Outputter.OutputCustomEvent(new RemoteOutputEventArgs(true));

                process.WaitForExit();
                process.Close();
                process = null;
            }
            else
            {
                Output.Error(ScaffoldResources.VSDidNotStart);
                return false;
            }

            return true;
        }

        private bool StartScaffoldAndRemote()
        {
            Process process = null;
            string programName;
            
            if (runningFromCommandLine)
                programName = Assembly.GetEntryAssembly().Location;
            else
            {
                // Look for scaffold.exe in the same directory as this assembly
                ParsedPath path = new ParsedPath(Assembly.GetExecutingAssembly().Location, PathType.File);
                
                programName = path.RootAndDirectory + scaffoldExe;
            }
            
            using (RemoteOutputter remoteOutputter = new RemoteOutputter(this.Output.Outputter))
            {
                try
                {
                    ProcessStartInfo startInfo = new ProcessStartInfo(programName, Parser.Arguments);

                    startInfo.UseShellExecute = false;
                    startInfo.EnvironmentVariables[scaffoldEnvironmentVar] = "RemotingUrl=" + remoteOutputter.RemotingUrl;

                    process = Process.Start(startInfo);
                    
                    if (process == null)
                    {
                        Output.Error(ScaffoldResources.ScaffoldDidNotStart);
                        return false;
                    }
                    
                    if (this.Wait)
                    {
                        // We pass the wait flag into the remote process so that it will display the wait message.
                        // It will wait on VS to exit, and we will wait on it.
                        process.WaitForExit();
                    }
                    else
                    {
                        // Wait for Scaffold to reach blocking point then continue
                        WaitHandle[] waitHandles = new WaitHandle[] 
                        { 
                            new ProcessWaitHandle(process),
                            remoteOutputter.BlockingEvent
                        };
                        
                        int n = WaitHandle.WaitAny(waitHandles);
                        
                        if (n == 0)
                            // The process exited unexpectedly; leave the scene of the crime
                            return false;
                    }
                    
                    process.Close();
                }
                catch (Win32Exception e)
                {
                    Output.Error(ScaffoldResources.UnableToStartScaffold, e.Message);
                    return false;
                }
            }
            
            return true;
        }

        private void GetBoolSetting(ScaffoldSection section, string setting, ref bool val)
        {
            try
            {
                for (int i = 0; i < section.Settings.Count; i++)
                {
                    TypeElement element = section.Settings[i];

                    if (String.Compare(element.Key, setting, true) == 0)
                    {
                        if (!TryParseTrueFalse(element.Value, ref val))
                            Output.Warning(ScaffoldResources.UnableToParseValueInConfig, element.Value, element.Key);

                        return;
                    }
                }
            }
            catch (ConfigurationException e)
            {
                Output.Warning(e.Message);
            }
        }

        private void GetStringSetting(IDictionary dict, string setting, ref string val)
        {
            string temp = (string)dict[setting];

            if (!String.IsNullOrEmpty(temp))
                val = temp;
        }

        private void GetBoolSetting(IDictionary dict, string setting, ref bool val)
        {
            string temp = (string)dict[setting];

            if (!String.IsNullOrEmpty(temp))
            {
                if (!TryParseTrueFalse(temp, ref val))
                    Output.Warning(ScaffoldResources.UnableToParseValueInEnvironment, temp, setting);
            }
        }

        private static bool TryParseTrueFalse(string s, ref bool b)
        {
            if ((String.Compare(s, ScaffoldResources.No, true) == 0 ||
                String.Compare(s, ScaffoldResources.False, true) == 0))
            {
                b = false;
                return true;
            }
            else if ((String.Compare(s, ScaffoldResources.Yes, true) == 0 ||
                String.Compare(s, ScaffoldResources.True, true) == 0))
            {
                b = true;
                return true;
            }

            return false;
        }

        private bool RealExecute()
        {
            // At this point, we need a program (the default argument)
            if (this.ScriptPath == null)
            {
                Output.Error(ScaffoldResources.NoSourceSpecified);
                return false;
            }

            // Fully qualify the program
            this.ScriptPath = this.ScriptPath.MakeFullPath();

            if (this.ScriptPath.File == String.Empty)
            {
                Output.Error(ScaffoldResources.NoEmptyFilename);
                return false;
            }
            else if (this.ScriptPath.HasWildcards)
            {
                Output.Error(ScaffoldResources.NoSourceWildcards);
                return false;
            }
            else if (this._Language == ScaffoldLanguage.Unknown)
            {
                Output.Error(ScaffoldResources.FileNameMustEndIn);
                return false;
            }

            // TODO-johnls-12/15/2007: More project types coming soon...?
            if (this._Language != ScaffoldLanguage.CSharp)
            {
                Output.Error(ScaffoldResources.OnlyCSharpSourcesSupported);
                return false;
            }

            // Check that VS is installed and get information about it
            VisualStudioInfo vsInfo = GetVisualStudioInfo();

            if (vsInfo == null)
                // Error already logged
                return false;

            // Create a temporary directory in which to put our scaffold project
            ParsedPath projectDir;
            Random rand = new Random();

            do
            {
                projectDir = new ParsedPath(String.Format("{0}Scaffold_{1:X8}", this.ScriptPath.RootAndDirectory, rand.Next()), PathType.Directory);
            }
            while (Directory.Exists(projectDir));

            Directory.CreateDirectory(projectDir);

            if (!CreateProjectFiles(vsInfo, projectDir))
                return false;

            StartDevenvAndWait(vsInfo, new ParsedPath(
                projectDir.RootAndDirectory + this.ScriptPath.File + ".sln", PathType.File));

            DeleteProjectFiles(projectDir);
            CleanUpProjectMruList(vsInfo);

            return true;
        }

        private void CleanUpProjectMruList(VisualStudioInfo vsInfo)
        {
            string mruKey = String.Format(@"Software\Microsoft\VisualStudio\{0}.{1}\ProjectMRUList", 
                vsInfo.Version.ProductMajorPart, vsInfo.Version.ProductMinorPart);
                
            RegistryKey key = Registry.CurrentUser.OpenSubKey(mruKey, true);
            
            if (this.Verbose && key == null)
            {
                Output.Warning(ScaffoldResources.UnableToRemoveProjectMruInformation);
                return;
            }
            
            string[] names = key.GetValueNames();
            int totalEntries = names.Length;
            // TODO-johnls-1/27/2008: The {1,8} is because before 2.0 we weren't making the random number 8 digits.  
            // Can be removed in a future release to just {8}.
            Regex regex = new Regex(@"Scaffold_[0-9A-F]{1,8}");
            
            // Delete all the MRU projects that contain a Scaffold sub-directory
            foreach (string name in names)
            {
                string val = key.GetValue(name) as string;
                
                if (val != null && regex.IsMatch(val))
                {
                    key.DeleteValue(name);
                    totalEntries--;
                }
            }
            
            // Renumber the remaining values
            for (int i = 1; i <= totalEntries; i++)
            {
                string name = "File" + i.ToString();
                
                // Does a key already exist with this index?
                if (key.GetValue(name) != null)
                    continue;
                    
                // Look for the next value 
                for (int j = 1; j < 255; j++)
                {
                    string oldName = "File" + (i + j).ToString();
                    object obj = key.GetValue(oldName);
                    
                    if (obj != null)
                    {
                        // Move the value into the next correct position
                        key.SetValue(name, obj, RegistryValueKind.ExpandString);
                        key.DeleteValue(oldName);
                        break;
                    }
                }
            }
        }

        #endregion

        #region Task Overrides
        /// <summary>
        /// Execute the task
        /// </summary>
        /// <returns>true if task succeeds, false otherwise.</returns>
        public bool Execute()
        {
            if (!runningFromCommandLine)
            {
                // Set the command name to the task name and display the canonical comamnd line
                Parser.CommandName = Parser.TaskName;

                string arguments = Parser.Arguments;
                Output.Message(MessageImportance.Low, Parser.CommandName + Parser.Arguments);
            }

            // If the help option appears anywhere, just show the help
            if (this.ShowHelp)
            {
                Output.Message(Parser.LogoBanner);
                Output.Message(Parser.Usage);
                Output.Message("");
                return false;
            }

            if (NoRemoting)
            {
                return RealExecute();
            }
            else if (RemotingUrl == null) 
            {
                // If we are not the second instance of Scaffold, then start it and return
                return StartScaffoldAndRemote();
            }
            else
            {
                // Otherwise swap in the remote outputter and continue
                using (RemoteOutputter remoteOutputter = new RemoteOutputter(RemotingUrl))
                { 
                    this.Output.Outputter = remoteOutputter;
                    
                    return RealExecute();
                }
            }
        }

        #endregion
        
        #region IProcessConfiguration Members

        /// <summary>
        /// Process th e
        /// </summary>
        /// <param name="userLevel"></param>
        /// <returns></returns>
        bool IProcessConfiguration.ProcessConfiguration(System.Configuration.ConfigurationUserLevel userLevel)
        {
            try
            {
                Configuration config = ConfigurationManager.OpenExeConfiguration(userLevel);

                if (!config.HasFile)
                    return true;

                ConfigurationSection tempSection = null;
                ScaffoldSection section = null;

                try
                {
                    tempSection = config.GetSection("scaffold");
                    section = tempSection as ScaffoldSection;
                }
                catch (ConfigurationException e)
                {
                    Output.Error(ScaffoldResources.ProblemLoadingExeConfiguration, e.Message);
                }

                if (section == null)
                {
                    if (tempSection != null)
                    {
                        Output.Warning(ScaffoldResources.ScaffoldSectionPresentButWrongType);
                    }
                    
                    // Otherwise the section just isn't there
                    return true;
                }

                GetBoolSetting(section, "Verbose", ref verbose);
                GetBoolSetting(section, "ScrapeReferences", ref scrapeReferences);
            }
            catch (ConfigurationErrorsException)
            {
                Output.Warning(ScaffoldResources.ApplicationConfigurationCouldNotBeLoaded);
                return false;
            }

            return true;
        }

        #endregion

        #region IProcessCommandLine Members

        bool IProcessCommandLine.ProcessCommandLine(string[] args)
        {
            runningFromCommandLine = true;

            CommandLineParser parser = new CommandLineParser(typeof(Scaffold), typeof(ScaffoldResources));

            try
            {
                Parser.ParseAndSetTarget(args, this);
            }
            catch (CommandLineArgumentException e)
            {
                Output.Error(e.Message);
                return false;
            }

            return true;
        }

        #endregion

        #region IProcessEnvironment Members

        bool IProcessEnvironment.ProcessEnvironment()
        {
            string allVars = Environment.GetEnvironmentVariable(scaffoldEnvironmentVar);

            if (allVars == null)
                return true;

            string[] vars = allVars.Split(';');

            ListDictionary environ = new ListDictionary();

            if (vars.Length > 0)
            {
                foreach (string var in vars)
                {
                    string[] parts = var.Split('=');

                    if (parts.Length == 1)
                    {
                        environ[parts[0].Trim()] = String.Empty;
                    }
                    else if (parts.Length == 2 && parts[0].Length > 0)
                    {
                        environ[parts[0].Trim()] = parts[1].Trim();
                    }
                }
            }

            if (environ.Count == 0)
                return true;

            GetStringSetting(environ, "RemotingUrl", ref remotingUrl);
            GetBoolSetting(environ, "NoRemoting", ref noRemoting);

            return true;
        }

        #endregion
    }
}
